package com.example.service;

import com.example.model.WebUser;
import com.example.repository.WebUserRepository;
import com.example.security.JwtUtil;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.io.StringReader;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Date;
import java.util.Optional;
import java.util.regex.Pattern;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.nio.file.Files;
import java.nio.file.Paths;

@Service
public class UserService {

    private static final Logger logger = LoggerFactory.getLogger(UserService.class);

    private final WebUserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtUtil jwtUtil;

    @Value("${certificate.root.key.path}")
    private String rootKeyPath;

    @Value("${certificate.root.cert.path}")
    private String rootCertPath;

    @Value("${certificate.root.key.password}")
    private String rootKeyPassword;

    public UserService(WebUserRepository userRepository, PasswordEncoder passwordEncoder, JwtUtil jwtUtil) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.jwtUtil = jwtUtil;
    }

    public String signUp(String username, String email, String password) {
        if (userRepository.existsByUsername(username)) {
            throw new RuntimeException("Username already exists");
        }
        if (userRepository.existsByEmail(email)) {
            throw new RuntimeException("Email already exists");
        }
        WebUser user = new WebUser();
        user.setUsername(username);
        user.setEmail(email);
        user.setPassword(passwordEncoder.encode(password));
        userRepository.save(user);
        return "Account created successfully";
    }

    public String signIn(String username, String password) {
        Optional<WebUser> userOpt = userRepository.findByUsername(username);
        if (!userOpt.isPresent()) {
            throw new RuntimeException("User not found");
        }

        WebUser user = userOpt.get();
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new RuntimeException("Invalid password");
        }

        return jwtUtil.generateToken(username);
    }

    public void signOut(String token) {
        jwtUtil.blacklistToken(token);
    }

    public WebUser getUserFromToken(String token) {
        if (!jwtUtil.validateToken(token)) {
            throw new RuntimeException("Invalid or expired token");
        }
        String username = jwtUtil.extractUsername(token);
        return userRepository.findByUsername(username)
                .orElseThrow(() -> new RuntimeException("User not found"));
    }

    public void processCredential(String token, MultipartFile csrFile) throws IOException {
        logger.debug("Processing credential for token: {}", token);
        WebUser user = getUserFromToken(token);
        logger.debug("Found user: {}", user.getUsername());
        
        // Generate HMAC key
        byte[] hmacKey = new byte[32];
        new SecureRandom().nextBytes(hmacKey);
        String encodedKey = Base64.getEncoder().encodeToString(hmacKey);
        logger.debug("Generated HMAC key");
        
        try {
            // Check if root key and cert files exist
            File rootKeyFile = new File(rootKeyPath);
            File rootCertFile = new File(rootCertPath);
            
            if (!rootKeyFile.exists()) {
                logger.error("Root key file not found at: {}", rootKeyPath);
                throw new RuntimeException("Root key file not found");
            }
            if (!rootCertFile.exists()) {
                logger.error("Root certificate file not found at: {}", rootCertPath);
                throw new RuntimeException("Root certificate file not found");
            }

            // Parse CSR and validate CN
            PKCS10CertificationRequest csr = parseCsr(csrFile.getBytes());
            String csrCN = extractCN(csr.getSubject().toString());
            logger.debug("CSR CN: {}, Username: {}", csrCN, user.getUsername());
            
            if (!csrCN.equals(user.getUsername())) {
                throw new RuntimeException("CSR Common Name does not match the logged-in username");
            }

            // Process CSR and generate certificate
            byte[] certificate = generateCertificate(csrFile.getBytes());
            logger.debug("Generated certificate");
            
            user.setHmacKey(encodedKey);
            user.setCertificate(certificate);
            userRepository.save(user);
            logger.debug("Saved user credentials");
        } catch (Exception e) {
            logger.error("Certificate generation failed", e);
            throw new RuntimeException("Failed to generate certificate: " + e.getMessage());
        }
    }

    private String extractCN(String subject) {
        // Subject format example: "C=CN,ST=Shaanxi,L=Xi'an,O=test,CN=testuser"
        String[] parts = subject.split(",");
        for (String part : parts) {
            if (part.trim().startsWith("CN=")) {
                return part.trim().substring(3);
            }
        }
        throw new RuntimeException("No Common Name (CN) found in CSR");
    }

    private PKCS10CertificationRequest parseCsr(byte[] csrData) {
        try {
            String csrString = new String(csrData).trim();
            byte[] decodedCsr;
            
            if (csrString.startsWith("-----BEGIN")) {
                // PEM format
                String base64Content = csrString
                    .replace("-----BEGIN CERTIFICATE REQUEST-----", "")
                    .replace("-----END CERTIFICATE REQUEST-----", "")
                    .replaceAll("\\s+", "");
                decodedCsr = Base64.getDecoder().decode(base64Content);
            } else {
                // Raw base64 format
                decodedCsr = Base64.getDecoder().decode(csrString.replaceAll("\\s+", ""));
            }
            
            PKCS10CertificationRequest csr = new PKCS10CertificationRequest(decodedCsr);
            logger.debug("Successfully parsed CSR");
            logger.debug("CSR subject: {}", csr.getSubject());
            return csr;
        } catch (Exception e) {
            logger.error("Failed to parse CSR", e);
            throw new RuntimeException("Failed to parse CSR: " + e.getMessage());
        }
    }

    private byte[] generateCertificate(byte[] csrData) {
        PKCS10CertificationRequest csr;
        PrivateKey privateKey;
        X509CertificateHolder rootCertHolder;

        // Step 1: Parse CSR
        try {
            logger.debug("CSR data length: {}", csrData.length);
            String csrString = new String(csrData).trim();
            logger.debug("CSR content: {}", csrString);
            
            byte[] decodedCsr;
            if (csrString.startsWith("-----BEGIN")) {
                // It's in PEM format, use PEMParser
                try (PEMParser pemParser = new PEMParser(new StringReader(csrString))) {
                    Object pemObject = pemParser.readObject();
                    if (!(pemObject instanceof PKCS10CertificationRequest)) {
                        throw new RuntimeException("Invalid CSR format");
                    }
                    csr = (PKCS10CertificationRequest) pemObject;
                }
            } else {
                // It's in raw base64 format
                decodedCsr = Base64.getDecoder().decode(csrString.replaceAll("\\s+", ""));
                csr = new PKCS10CertificationRequest(decodedCsr);
            }
            logger.debug("Successfully parsed CSR");
            logger.debug("CSR subject: {}", csr.getSubject());
        } catch (Exception e) {
            logger.error("Failed to parse CSR", e);
            throw new RuntimeException("Failed to parse CSR: " + e.getMessage());
        }

        // Step 2: Load and parse private key
        try {
            String keyContent = new String(Files.readAllBytes(Paths.get(rootKeyPath))).trim();
            logger.debug("Loading private key from: {}", rootKeyPath);
            
            // Decode base64 key content
            byte[] keyBytes = Base64.getDecoder().decode(keyContent.replaceAll("\\s+", ""));
            
            // Parse as PKCS#8 private key
            PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(keyBytes);
            privateKey = new JcaPEMKeyConverter()
                .setProvider(BouncyCastleProvider.PROVIDER_NAME)
                .getPrivateKey(privateKeyInfo);
            logger.debug("Successfully loaded private key");
            logger.debug("Private key algorithm: {}", privateKey.getAlgorithm());
        } catch (Exception e) {
            logger.error("Failed to load private key", e);
            throw new RuntimeException("Failed to load private key: " + e.getMessage());
        }

        // Step 3: Load and parse root certificate
        try {
            String certContent = new String(Files.readAllBytes(Paths.get(rootCertPath))).trim();
            logger.debug("Loading root certificate from: {}", rootCertPath);
            
            // Decode base64 certificate content
            byte[] certBytes = Base64.getDecoder().decode(certContent.replaceAll("\\s+", ""));
            rootCertHolder = new X509CertificateHolder(certBytes);
            logger.debug("Successfully loaded root certificate");
            logger.debug("Root certificate subject: {}", rootCertHolder.getSubject());
        } catch (Exception e) {
            logger.error("Failed to load root certificate", e);
            throw new RuntimeException("Failed to load root certificate: " + e.getMessage());
        }

        // Step 4: Generate certificate
        try {
            // Generate random serial number
            byte[] serialBytes = new byte[20];
            new SecureRandom().nextBytes(serialBytes);
            BigInteger serialNumber = new BigInteger(1, serialBytes);

            // Set validity period (1 year)
            Date startDate = new Date(System.currentTimeMillis());
            Date endDate = new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000);

            // Create certificate builder
            X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
                rootCertHolder.getSubject(),
                serialNumber,
                startDate,
                endDate,
                csr.getSubject(),
                csr.getSubjectPublicKeyInfo()
            );
            logger.debug("Created certificate builder");

            // Add extensions
            certBuilder.addExtension(
                Extension.basicConstraints,
                true,
                new BasicConstraints(false)
            );

            certBuilder.addExtension(
                Extension.keyUsage,
                true,
                new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment)
            );
            logger.debug("Added certificate extensions");

            // Create certificate signer
            ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
                .setProvider(BouncyCastleProvider.PROVIDER_NAME)
                .build(privateKey);
            logger.debug("Created content signer");

            // Generate certificate
            X509CertificateHolder certHolder = certBuilder.build(signer);
            logger.debug("Generated certificate");

            // Convert to PEM format
            StringWriter stringWriter = new StringWriter();
            try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {
                pemWriter.writeObject(certHolder);
            }
            logger.debug("Converted certificate to PEM format");

            return stringWriter.toString().getBytes();
        } catch (Exception e) {
            logger.error("Failed to generate certificate", e);
            throw new RuntimeException("Failed to generate certificate: " + e.getMessage());
        }
    }
} 